﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using Neitzel.Network;

namespace Neitzel.Irc
{
    /// <summary>
    /// Class that is doing all IRC Related Communication.
    /// </summary>
    public class IrcConnection : IDisposable, INotifyPropertyChanged    
    {
        #region Fields

        /// <summary>
        /// Tracesource for all log messages.
        /// </summary>
        private static readonly TraceSource Logger = new TraceSource(IrcConstants.TraceSourceName);

        /// <summary>
        /// Connection to the IRC Server.
        /// </summary>
        private TcpIpClientConnection _connection;

        #endregion

        #region Lifetime

        /// <summary>
        /// Initializes a new instance of the IrcConnection class.
        /// Port defaults to 6667, Hostname is empty and must be set!
        /// </summary>
        public IrcConnection()
        {
            _port = 6667;
        }

        /// <summary>
        /// Initializes a new instance of the IrcConnection class.
        /// Port defaults to 6667
        /// </summary>
        /// <param name="hostName">Hostname / IP to connect to</param>
        public IrcConnection(string hostName)
            : this()
        {
            _serverName = hostName;
        }

        /// <summary>
        /// Initializes a new instance of the IrcConnection class.
        /// </summary>
        /// <param name="hostName">Hostname / IP to connect to</param>
        /// <param name="port">Port to use</param>
        public IrcConnection(string hostName, int port)
            : this(hostName)
        {
            _port = port;
        }

        /// <summary>
        /// Initializes a new instance of the IrcConnection class.
        /// </summary>
        /// <param name="config">Configuration information through ServerConfig</param>
        public IrcConnection(ServerConfig config)
            : this(config.HostName, config.Port)
        {
        }

        /// <summary>
        /// Finalizes an instance of the IrcConnection class.
        /// </summary>
        ~IrcConnection()
        {
            // This should never be called. In Debug we should warn!
            Debug.Print("IrcConnection wasn't disposed!");

            // Call dispose
            Dispose(false);
        }

        /// <summary>
        /// Frees resources held by this instance.
        /// </summary>
        /// <param name="disposing">True when called via Dispose, false when called via finalizer.</param>
        protected virtual void Dispose(bool disposing)
        {
            // Dispose managed types during dispose
            if (disposing)
            {
                if (_connection != null)
                {
                    _connection.MessageReceived -= OnMessageReceived;
                    _connection.Close();
                }
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Event that a Message was Received
        /// </summary>
        public event EventHandler<IrcMessageEventArgs> IrcMessageReceived;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the name of the irc server.
        /// </summary>
        public string ServerName
        {
            get { return _serverName; }
            set { _serverName = value; OnPropertyChanged(this, new PropertyChangedEventArgs(nameof(ServerName))); }
        }
        private string _serverName;

        /// <summary>
        /// Gets or sets the port of the irc server to use.
        /// </summary>
        public int Port
        {
            get { return _port; }
            set { _port = value; OnPropertyChanged(this, new PropertyChangedEventArgs(nameof(Port))); }
        }
        private int _port;

        /// <summary>
        /// Gets a value indicating whether we an connection is established or not.
        /// </summary>
        public bool Connected => _connection?.Connected ?? false;

        #endregion

        #region Public Members 

        /// <summary>
        /// Close / Dispose this instance.
        /// </summary>
        public void Dispose()
        {
            // Full dispose
            Dispose(true);

            // Suppress finalizer (no longer necessary)
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Close / Dispose this instance.
        /// </summary>
        public void Close()
        {
            Dispose();
        }

        /// <summary>
        /// Connect to the IRC ServerConfig!
        /// </summary>
        public void Connect()
        {
            Trace.TraceInformation(Properties.Resources.StringIrcConnectionConnecting, ServerName, Port);

            try
            {
                _connection = new TcpIpClientConnection(ServerName, Port);
                _connection.MessageReceived += OnMessageReceived;
                _connection.Connect();
            }
            catch (Exception ex)
            {
                // Error when connecting to the server ....
                Trace.TraceError(Properties.Resources.StringExceptionOccured, ex.Message, ex.StackTrace);
                throw;
            }
        }

        /// <summary>
        /// Send a Message to the IRC ServerConfig
        /// </summary>
        /// <param name="message">A IRC Message to send</param>
        public void SendMessage(IrcMessage message)
        {
            // validate
            if (message == null) throw new ArgumentNullException(nameof(message));
            
            try
            {
                Trace.TraceInformation(Properties.Resources.StringIrcConnectionSending, message);
                _connection.SendMessage(message.ToByteArray());
            }
            catch (Exception ex)
            {
                 // Exception while sending Message!
                Trace.TraceError(Properties.Resources.StringExceptionOccured, ex.Message, ex.StackTrace);
                throw;
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Send the MessageReceived Event
        /// </summary>
        /// <param name="e">IrcMessageEventArgs to send.</param>
        protected virtual void OnIrcMessageReceived(IrcMessageEventArgs e)
        {
            // Local copy to prevent race conditions
            var handler = IrcMessageReceived;

            // raise the event
            handler?.Invoke(this, e);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Store the received text to get full messages.
        /// </summary>
        private String _receivedTextBuffer = "";

        /// <summary>
        /// Handle incomming messages and fire OnIrcMessageReceived
        /// </summary>
        /// <param name="sender">Sender of the message.</param>
        /// <param name="args">The received bytes argument.</param>
        private void OnMessageReceived(object sender, BytesReceivedEventArgs args)
        {
            // Add text to the received text buffer
            _receivedTextBuffer += (Encoding.ASCII.GetString(args.ReceivedBytes, 0, args.ReceivedBytes.Length));

            // As long as there is a "\r\n" in the Buffer
            int locationCrlf;
            while ((locationCrlf = _receivedTextBuffer.IndexOf("\r\n", StringComparison.OrdinalIgnoreCase)) > 0)
            {
                // The received Message is start of buffer till \r\n
                string message = _receivedTextBuffer.Substring(0, locationCrlf);

                // The Buffer is now starting behind the \r\n combination
                _receivedTextBuffer = _receivedTextBuffer.Substring(locationCrlf + 2);

                // Trigger the event that we got a new IrcMessage
                Trace.TraceInformation(Properties.Resources.StringIrcConnectionReceived, message);
                OnIrcMessageReceived(new IrcMessageEventArgs(new IrcMessage(message)));
            }
        }

        #endregion

        #region INotifyPropertyChanged parts

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // prevent racing conditions
            var handler = PropertyChanged;

            // trigger event
            handler?.Invoke(sender, e);
        }

        #endregion
    }
}
